/**
* Copyright (c) By zengqh.
*
* This program is just for fun or demo, in the hope that it  
* will be useful, you can redistribute it and/or modify freely.
*
* Time: 2013/02/18
* File: frustum.cpp
**/

#include "enn_frustum.h"

#if ENN_PLATFORM == ENN_PLATFORM_WIN32
#ifdef near
#undef near
#endif

#ifdef far
#undef far
#endif
#endif

namespace enn
{
inline vec3f ClipEdgeZ(const vec3f& v0, const vec3f& v1, float clipZ)
{
	return vec3f(
		v1.x_ + (v0.x_ - v1.x_) * ((clipZ - v1.z_) / (v0.z_ - v1.z_)),
		v1.y_ + (v0.y_ - v1.y_) * ((clipZ - v1.z_) / (v0.z_ - v1.z_)),
		clipZ
		);
}

void ProjectAndMergeEdge(vec3f v0, vec3f v1, Rect& rect, const Matrix4& projection)
{
	// Check if both vertices behind near plane
	if (v0.z_ < ENN_MIN_NEARPLANE && v1.z_ < ENN_MIN_NEARPLANE)
		return;

	// Check if need to clip one of the vertices
	if (v1.z_ < ENN_MIN_NEARPLANE)
		v1 = ClipEdgeZ(v1, v0, ENN_MIN_NEARPLANE);
	else if (v0.z_ < ENN_MIN_NEARPLANE)
		v0 = ClipEdgeZ(v0, v1, ENN_MIN_NEARPLANE);

	// Project, perspective divide and merge
	vec3f tV0(projection * v0);
	vec3f tV1(projection * v1);
	rect.Merge(vec2f(tV0.x_, tV0.y_));
	rect.Merge(vec2f(tV1.x_, tV1.y_));
}

Frustum::Frustum() :
defined_(false)
{
}

Frustum::Frustum(const Frustum& frustum)
{
	*this = frustum;
}

Frustum& Frustum::operator = (const Frustum& rhs)
{
	for (unsigned i = 0; i < NUM_FRUSTUM_PLANES; ++i)
		planes_[i] = rhs.planes_[i];
	for (unsigned i = 0; i < NUM_FRUSTUM_VERTICES; ++i)
		vertices_[i] = rhs.vertices_[i];
	defined_ = rhs.defined_;

	return *this;
}

void Frustum::Define(float fov, float aspectRatio, float zoom, float nearZ, float farZ, const Matrix3x4& transform)
{
	nearZ = Max(nearZ, 0.0f);
	farZ = Max(farZ, nearZ);
	float halfViewSize = tanf(fov * (float)ENN_DEGTORAD * 0.5f) / zoom;
	vec3f near, far;

	near.z_ = nearZ;
	near.y_ = near.z_ * halfViewSize;
	near.x_ = near.y_ * aspectRatio;
	far.z_ = farZ;
	far.y_ = far.z_ * halfViewSize;
	far.x_ = far.y_ * aspectRatio;

	Define(near, far, transform);
}

void Frustum::Define(const vec3f& near, const vec3f& far, const Matrix3x4& transform)
{
	vertices_[0] = transform * near;
	vertices_[1] = transform * vec3f(near.x_, -near.y_, near.z_);
	vertices_[2] = transform * vec3f(-near.x_, -near.y_, near.z_);
	vertices_[3] = transform * vec3f(-near.x_, near.y_, near.z_);
	vertices_[4] = transform * far;
	vertices_[5] = transform * vec3f(far.x_, -far.y_, far.z_);
	vertices_[6] = transform * vec3f(-far.x_, -far.y_, far.z_);
	vertices_[7] = transform * vec3f(-far.x_, far.y_, far.z_);

	UpdatePlanes();
}

void Frustum::Define(const BoundingBox& box, const Matrix3x4& transform)
{
	vertices_[0] = transform * vec3f(box.max_.x_, box.max_.y_, box.min_.z_);
	vertices_[1] = transform * vec3f(box.max_.x_, box.min_.y_, box.min_.z_);
	vertices_[2] = transform * vec3f(box.min_.x_, box.min_.y_, box.min_.z_);
	vertices_[3] = transform * vec3f(box.min_.x_, box.max_.y_, box.min_.z_);
	vertices_[4] = transform * vec3f(box.max_.x_, box.max_.y_, box.max_.z_);
	vertices_[5] = transform * vec3f(box.max_.x_, box.min_.y_, box.max_.z_);
	vertices_[6] = transform * vec3f(box.min_.x_, box.min_.y_, box.max_.z_);
	vertices_[7] = transform * vec3f(box.min_.x_, box.max_.y_, box.max_.z_);

	UpdatePlanes();
}

void Frustum::DefineOrtho(float orthoSize, float aspectRatio, float zoom, float nearZ, float farZ, const Matrix3x4& transform)
{
	nearZ = Max(nearZ, 0.0f);
	farZ = Max(farZ, nearZ);
	float halfViewSize = orthoSize * 0.5f / zoom;
	vec3f near, far;

	near.z_ = nearZ;
	far.z_ = farZ;
	far.y_ = near.y_ = halfViewSize;
	far.x_ = near.x_ = near.y_ * aspectRatio;

	Define(near, far, transform);
}

void Frustum::Define(float fov, float aspectRatio, float zoom, float nearZ, float farZ, const Matrix4& transform)
{
	nearZ = Max(nearZ, 0.0f);
	farZ = Max(farZ, nearZ);
	float halfViewSize = tanf(fov * static_cast<float>ENN_DEGTORAD * 0.5f) / zoom;
	vec3f near, far;

	near.z_ = nearZ;
	near.y_ = near.z_ * halfViewSize;
	near.x_ = near.y_ * aspectRatio;
	far.z_ = farZ;
	far.y_ = far.z_ * halfViewSize;
	far.x_ = far.y_ * aspectRatio;

	Define(near, far, transform);
}

void Frustum::Define(const vec3f& near, const vec3f& far, const Matrix4& transform)
{
	vertices_[0] = transform * near;
	vertices_[1] = transform * vec3f(near.x_, -near.y_, near.z_);
	vertices_[2] = transform * vec3f(-near.x_, -near.y_, near.z_);
	vertices_[3] = transform * vec3f(-near.x_, near.y_, near.z_);
	vertices_[4] = transform * far;
	vertices_[5] = transform * vec3f(far.x_, -far.y_, far.z_);
	vertices_[6] = transform * vec3f(-far.x_, -far.y_, far.z_);
	vertices_[7] = transform * vec3f(-far.x_, far.y_, far.z_);

	UpdatePlanes();
}

void Frustum::Define(const BoundingBox& box, const Matrix4& transform)
{
	vertices_[0] = transform * vec3f(box.max_.x_, box.max_.y_, box.min_.z_);
	vertices_[1] = transform * vec3f(box.max_.x_, box.min_.y_, box.min_.z_);
	vertices_[2] = transform * vec3f(box.min_.x_, box.min_.y_, box.min_.z_);
	vertices_[3] = transform * vec3f(box.min_.x_, box.max_.y_, box.min_.z_);
	vertices_[4] = transform * vec3f(box.max_.x_, box.max_.y_, box.max_.z_);
	vertices_[5] = transform * vec3f(box.max_.x_, box.min_.y_, box.max_.z_);
	vertices_[6] = transform * vec3f(box.min_.x_, box.min_.y_, box.max_.z_);
	vertices_[7] = transform * vec3f(box.min_.x_, box.max_.y_, box.max_.z_);

	UpdatePlanes();
}

void Frustum::DefineOrtho(float orthoSize, float aspectRatio, float zoom, float nearZ, float farZ, const Matrix4& transform)
{
	nearZ = Max(nearZ, 0.0f);
	farZ = Max(farZ, nearZ);
	float halfViewSize = orthoSize * 0.5f / zoom;
	vec3f near, far;

	near.z_ = nearZ;
	far.z_ = farZ;
	far.y_ = near.y_ = halfViewSize;
	far.x_ = near.x_ = near.y_ * aspectRatio;

	Define(near, far, transform);
}

void Frustum::Transform(const Matrix3& transform)
{
	for (unsigned i = 0; i < NUM_FRUSTUM_VERTICES; ++i)
		vertices_[i] = transform * vertices_[i];

	UpdatePlanes();
}

void Frustum::Transform(const Matrix3x4& transform)
{
	for (unsigned i = 0; i < NUM_FRUSTUM_VERTICES; ++i)
		vertices_[i] = transform * vertices_[i];

	UpdatePlanes();
}

Frustum Frustum::Transformed(const Matrix3& transform) const
{
	Frustum transformed;
	for (unsigned i = 0; i < NUM_FRUSTUM_VERTICES; ++i)
		transformed.vertices_[i] = transform * vertices_[i];

	transformed.UpdatePlanes();
	return transformed;
}

Frustum Frustum::Transformed(const Matrix3x4& transform) const
{
	Frustum transformed;
	for (unsigned i = 0; i < NUM_FRUSTUM_VERTICES; ++i)
		transformed.vertices_[i] = transform * vertices_[i];

	transformed.UpdatePlanes();
	return transformed;
}

Rect Frustum::Projected(const Matrix4& projection) const
{
	Rect rect;

	ProjectAndMergeEdge(vertices_[0], vertices_[4], rect, projection);
	ProjectAndMergeEdge(vertices_[1], vertices_[5], rect, projection);
	ProjectAndMergeEdge(vertices_[2], vertices_[6], rect, projection);
	ProjectAndMergeEdge(vertices_[3], vertices_[7], rect, projection);
	ProjectAndMergeEdge(vertices_[4], vertices_[5], rect, projection);
	ProjectAndMergeEdge(vertices_[5], vertices_[6], rect, projection);
	ProjectAndMergeEdge(vertices_[6], vertices_[7], rect, projection);
	ProjectAndMergeEdge(vertices_[7], vertices_[4], rect, projection);

	return rect;
}

void Frustum::UpdatePlanes()
{
	planes_[PLANE_NEAR].Define(vertices_[2], vertices_[1], vertices_[0]);
	planes_[PLANE_LEFT].Define(vertices_[3], vertices_[7], vertices_[6]);
	planes_[PLANE_RIGHT].Define(vertices_[1], vertices_[5], vertices_[4]);
	planes_[PLANE_UP].Define(vertices_[0], vertices_[4], vertices_[7]);
	planes_[PLANE_DOWN].Define(vertices_[6], vertices_[5], vertices_[1]);
	planes_[PLANE_FAR].Define(vertices_[5], vertices_[6], vertices_[7]);
	defined_ = true;
}
}